import Foundation
@testable import GraphQL
import Testing

@Suite struct SchemaPrinterTests {
    @Test func printsMinimalAST() {
        let ast = ScalarTypeDefinition(
            name: .init(value: "foo")
        )
        #expect(print(ast: ast) == "scalar foo")
    }

    @Test func printsKitchenSinkWithoutAlteringAST() throws {
        guard
            let url = Bundle.module.url(
                forResource: "schema-kitchen-sink",
                withExtension: "graphql"
            ),
            let kitchenSink = try? String(contentsOf: url, encoding: .utf8)
        else {
            Issue.record("Could not load kitchen sink")
            return
        }

        let ast = try parse(source: kitchenSink, noLocation: true)

        let printed = print(ast: ast)
        let printedAST = try parse(source: printed, noLocation: true)

        #expect(printed == print(ast: printedAST))
        #expect(printed == #"""
        """This is a description of the schema as a whole."""
        schema {
          query: QueryType
          mutation: MutationType
        }

        """
        This is a description
        of the `Foo` type.
        """
        type Foo implements Bar & Baz & Two {
          "Description of the `one` field."
          one: Type
          """This is a description of the `two` field."""
          two(
            """This is a description of the `argument` argument."""
            argument: InputType!
          ): Type
          """This is a description of the `three` field."""
          three(argument: InputType, other: String): Int
          four(argument: String = "string"): String
          five(argument: [String] = ["string", "string"]): String
          six(argument: InputType = { key: "value" }): Type
          seven(argument: Int = null): Type
          eight(argument: OneOfInputType): Type
        }

        type AnnotatedObject @onObject(arg: "value") {
          annotatedField(arg: Type = "default" @onArgumentDefinition): Type @onField
        }

        type UndefinedType

        extend type Foo {
          seven(argument: [String]): Type
        }

        extend type Foo @onType

        interface Bar {
          one: Type
          four(argument: String = "string"): String
        }

        interface AnnotatedInterface @onInterface {
          annotatedField(arg: Type @onArgumentDefinition): Type @onField
        }

        interface UndefinedInterface

        extend interface Bar implements Two {
          two(argument: InputType!): Type
        }

        extend interface Bar @onInterface

        interface Baz implements Bar & Two {
          one: Type
          two(argument: InputType!): Type
          four(argument: String = "string"): String
        }

        union Feed = Story | Article | Advert

        union AnnotatedUnion @onUnion = A | B

        union AnnotatedUnionTwo @onUnion = A | B

        union UndefinedUnion

        extend union Feed = Photo | Video

        extend union Feed @onUnion

        scalar CustomScalar

        scalar AnnotatedScalar @onScalar

        extend scalar CustomScalar @onScalar

        enum Site {
          """This is a description of the `DESKTOP` value"""
          DESKTOP
          """This is a description of the `MOBILE` value"""
          MOBILE
          "This is a description of the `WEB` value"
          WEB
        }

        enum AnnotatedEnum @onEnum {
          ANNOTATED_VALUE @onEnumValue
          OTHER_VALUE
        }

        enum UndefinedEnum

        extend enum Site {
          VR
        }

        extend enum Site @onEnum

        input InputType {
          key: String!
          answer: Int = 42
        }

        input OneOfInputType @oneOf {
          string: String
          int: Int
        }

        input AnnotatedInput @onInputObject {
          annotatedField: Type @onInputFieldDefinition
        }

        input UndefinedInput

        extend input InputType {
          other: Float = 1.23e4 @onInputFieldDefinition
        }

        extend input InputType @onInputObject

        """This is a description of the `@skip` directive"""
        directive @skip(
          """This is a description of the `if` argument"""
          if: Boolean! @onArgumentDefinition
        ) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT

        directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT

        directive @include2(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT

        directive @myRepeatableDir(name: String!) repeatable on OBJECT | INTERFACE

        extend schema @onSchema

        extend schema @onSchema {
          subscription: SubscriptionType
        }
        """#)
    }
}
